﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls.WebParts;

using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.Workflow;

namespace EF
{
    public class EFWorkflowTimer : SPJobDefinition
    {
        #region constant variables
        public const string JOB_NAME = "EFWorkflowTimer";
        public const char JobName_SeparatorChar = ' ';

        public const string Configuration_ListTitle = @"EFWorkflowTimerCfg";
        public const string Configuration_ListDescription = @"EF SharePoint 2010 WorkflowTimer configuration settings";
        //public const string Configuration_ListUrl = @"/Lists/" + Configuration_ListTitle;
        public const string History_ListTitle = @"EFWorkflowTimerHistory";
        public const string History_ListDescription = @"EF SharePoint 2010 WorkflowTimer History";
        //public const string History_ListUrl = @"/Lists/" + History_ListTitle;

        public const string Job_ListTitle = @"EFWorkflowTimerJob";
        public const string Job_ListDescription = @"EF SharePoint 2010 WorkflowTimer Job Links";
        //public const string Job_ListUrl = @"/Lists/" + Job_ListTitle;

        //  EFWorkflowTimerCfg
        public const string Column_EFWorkflowTimerCfg_WorkflowType = @"WorkflowType";
        public const string Column_EFWorkflowTimerCfg_WorkflowName = @"WorkflowName";
        public const string Column_EFWorkflowTimerCfg_RecurringType = @"RecurringType";
        public const string Column_EFWorkflowTimerCfg_WebUrl = @"WebUrl";
        public const string Column_EFWorkflowTimerCfg_ViewUrl = @"ViewUrl";
        public const string Column_EFWorkflowTimerCfg_LastScanTime = @"LastScanTime";
        public const string Column_EFWorkflowTimerCfg_LastScanCount = @"LastScanCount";
        public const string Column_EFWorkflowTimerCfg_TraceHistory = @"TraceHistory";
        public const string Column_EFWorkflowTimerCfg_BussinessHourOnly = @"BussinessHourOnly";
        public const string Column_EFWorkflowTimerCfg_IsActive = @"IsActive";

        //  EFWorkflowTimerHistory
        public const string Column_EFWorkflowTimerHistory_WebTitle = @"WebTitle";
        public const string Column_EFWorkflowTimerHistory_WebUrl = @"WebUrl";
        public const string Column_EFWorkflowTimerHistory_ListTitle = @"ListTitle";
        public const string Column_EFWorkflowTimerHistory_Message = @"Message";
        public const string Column_EFWorkflowTimerHistory_ItemId = @"ItemId";
        public const string Column_EFWorkflowTimerHistory_ItemUrl = @"ItemUrl";

        //EFWorkflowTimerJob
        public const string Column_EFWorkflowTimerJob_AdminLink = @"AdminLink";

        public const string View_Portal = @"Portal";

        public const string WorkflowType_Site = @"Site";
        public const string WorkflowType_List = @"List";

        public const string RecurringType_Monthly = @"Monthly";
        public const string RecurringType_Weekly = @"Weekly";
        public const string RecurringType_Daily = @"Daily";
        public const string RecurringType_Hourly = @"Hourly";
        public const string RecurringType_Minutes = @"Minutes";

        public const string DocLib_SitePages_Title = @"Site Pages";
        public const string DocLib_SitePages_PathName = @"SitePages";
        public const string AdminPage_Name = @"EFWorkflowTimerAdminLink";
        public const string AdminPage_Title = "EF WorkflowTimer Admin Page";
        public const string AdminPage_Url = "SitePages/EFWorkflowTimerAdminLink.aspx";

        public const string Zone_TitleBar = @"TitleBar";
        public const string Zone_Header = @"Header";
        public const string Zone_LeftColumn = @"LeftColumn";
        public const string Zone_MiddleColumn = @"MiddleColumn";
        public const string Zone_RightColumn = @"RightColumn";
        public const string Zone_Footer = @"Footer";

        public enum ListViewWebPartToolbarType
        {
            Standard = 0,       //Standard
            FreeForm = 1,       //Freeform
            None = 2,           //None
            ShowToolbar = 4     //ShowToolbar
        }
        #endregion

        public string _strTimerName = string.Empty;
        public string _strJobName = string.Empty;

        #region constructor
        public EFWorkflowTimer()
            : base()
        {

        }

        public EFWorkflowTimer(string strJobName, SPService service, SPServer server, SPJobLockType targetType)
            : base(strJobName, service, server, targetType)
        {

        }

        public EFWorkflowTimer(string strJobName, SPWebApplication webApplication)
            : base(strJobName, webApplication, null, SPJobLockType.ContentDatabase)
        {
            //this.Name = strJobName;
            this.Title = strJobName;
        }
        #endregion

        #region common functions

        public void logHistory(SPWeb objSPWeb, string strMessage)
        {
            logInfo(objSPWeb, strMessage, string.Empty, _strJobName, string.Empty, string.Empty);
        }

        public void logDebugHistory(SPWeb objSPWeb, string strMessage)
        {
#if DEBUG
            logDebugHistory(objSPWeb, strMessage, string.Empty, string.Empty);
#endif
        }

        public void logDebugHistory(SPWeb objSPWeb, string strMessage, string strWorkflowType, string strWorkflowName)
        {
#if DEBUG
            System.Reflection.Assembly objAssembly = null;
            objAssembly = this.GetType().Assembly;
            FileInfo objFileInfo = new FileInfo(objAssembly.Location);
            string strVersionInfo = string.Empty;
            strVersionInfo = string.Format(@"debug - {0} - {1}", objFileInfo.CreationTime, objAssembly.GetName().Version);
            logDebugHistory(objSPWeb, strMessage, strVersionInfo, strWorkflowType, strWorkflowName);
#endif
        }

        public void logDebugHistory(SPWeb objSPWeb, string strMessage, string strVersionInfo, string strWorkflowType, string strWorkflowName)
        {
#if DEBUG
            logInfo(objSPWeb, strMessage, strVersionInfo, _strJobName, strWorkflowType, strWorkflowName);
#endif
        }

        public static void logInfo(SPWeb objSPWeb, string strMessage, string strVersionInfo, string strJobName, string strWorkflowType, string strWorkflowName)
        {
            if (string.IsNullOrEmpty(strMessage))
                return;

            string strLogMessage = strMessage;
            if (strLogMessage.Length > 230)
                strLogMessage = strLogMessage.Substring(0, 230);
            if (string.IsNullOrEmpty(strVersionInfo) == false)
            {
                strLogMessage = string.Format("{0} - {1}", strVersionInfo, strLogMessage);
            }

            SPList splistHistory = null;
            try
            {
                if (objSPWeb != null)
                {
                    using (SPSite objSPSite = new SPSite(objSPWeb.Site.ID))
                    {
                        using (SPWeb objSPWebRoot = objSPSite.RootWeb)
                        {
                            objSPWebRoot.AllowUnsafeUpdates = true;
                            splistHistory = objSPWebRoot.Lists.TryGetList(History_ListTitle);
                            if (splistHistory != null)
                            {
                                SPListItem itemHistory = splistHistory.AddItem();
                                itemHistory[SPBuiltInFieldId.Title] = strJobName;
                                itemHistory[Column_EFWorkflowTimerHistory_Message] = strLogMessage;
                                itemHistory[Column_EFWorkflowTimerHistory_WebTitle] = objSPWeb.Title;
                                itemHistory[Column_EFWorkflowTimerHistory_WebUrl] = objSPWeb.ServerRelativeUrl;
                                itemHistory.Update();
                            }
                            objSPWebRoot.AllowUnsafeUpdates = false;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                logTraceInfo(string.Format(@"ex, strWorkflowName={0}, strLogMessage={1}", strWorkflowName, strLogMessage));
                logTraceInfo(string.Format(@"ex.Message={0}", ex.Message));
                logTraceInfo(string.Format(@"ex.StackTrace={0}", ex.StackTrace));

                throw;
            }
        }

        public static void logTraceInfo(string strMessage)
        {
            if (string.IsNullOrEmpty(strMessage))
                return;

            SPDiagnosticsService diagSvc = SPDiagnosticsService.Local;
            diagSvc.WriteTrace(0,
                new SPDiagnosticsCategory(JOB_NAME, TraceSeverity.Monitorable, EventSeverity.Error),
                TraceSeverity.Monitorable,
                "{0}",
                new object[] { strMessage });
        }

        public void logWorkflowTimerHistory(SPWeb objSPWeb, SPListItem itemTarget, string strMessage)
        {
            try
            {
                using (SPSite objSPSite = new SPSite(objSPWeb.Site.ID))
                {
                    using (SPWeb objSPWebRoot = objSPSite.OpenWeb(objSPSite.RootWeb.ID))
                    {
                        objSPWebRoot.AllowUnsafeUpdates = true;
                        SPList splistHistory = objSPWebRoot.Lists.TryGetList(History_ListTitle);

                        SPListItem itemHistory = splistHistory.AddItem();
                        itemHistory[SPBuiltInFieldId.Title] = _strTimerName;
                        itemHistory[Column_EFWorkflowTimerHistory_WebTitle] = objSPWeb.Title;
                        itemHistory[Column_EFWorkflowTimerHistory_WebUrl] = objSPWeb.ServerRelativeUrl;
                        itemHistory[Column_EFWorkflowTimerHistory_Message] = strMessage;

                        if (itemTarget != null)
                        {
                            itemHistory[Column_EFWorkflowTimerHistory_ListTitle] = itemTarget.ParentList.Title;
                            itemHistory[Column_EFWorkflowTimerHistory_ItemId] = itemTarget.ID;
                            itemHistory[Column_EFWorkflowTimerHistory_ItemUrl] = itemTarget.Url;
                        }
                        itemHistory.Update();
                        objSPWebRoot.AllowUnsafeUpdates = false;
                    }
                }
            }
            catch (Exception ex)
            {
                logTraceInfo(string.Format(@"ex, strJobName={0}, itemTarget.Url={1}", _strTimerName, itemTarget.Url));
                logTraceInfo(string.Format(@"ex.Message={0}", ex.Message));
                logTraceInfo(string.Format(@"ex.StackTrace={0}", ex.StackTrace));

                throw;
            }

        }

        public static string getFullJobName(string strUrl)
        {
            return getFullJobName(strUrl, string.Empty);
        }

        public static string getFullJobName(string strUrl, string strRecurringType)
        {
            string strReturn = strUrl;
            strReturn = strReturn.Substring(strReturn.IndexOf(@"//") + 2);
            strReturn = strReturn.Replace(@":", @"_");
            strReturn = strReturn.Replace(@"/", @"_");
            strReturn = string.Format(@"{0}{1}{2}", EFWorkflowTimer.JOB_NAME, JobName_SeparatorChar, strReturn);

            if (string.IsNullOrEmpty(strRecurringType) == false)
            {
                strReturn += string.Format("{0}{1}", JobName_SeparatorChar, strRecurringType);
            }

            return strReturn;
        }

        public static string getServerRelativeUrl(SPWeb objSPWeb, string strWebRelativeUrl)
        {
            string strServerRelativeUrl = string.Empty;
            string strWebServerRelativeUrl = objSPWeb.ServerRelativeUrl;
            if (strWebServerRelativeUrl == @"/") strWebServerRelativeUrl = string.Empty;

            strServerRelativeUrl = strWebServerRelativeUrl + strWebRelativeUrl;
            return strServerRelativeUrl;
        }

        public static string replaceTags(SPListItem itemTarget, string strContent)
        {
            return replaceTags(itemTarget, strContent, false);
        }

        public static string replaceTags(SPListItem itemTarget, string strContent, bool boolUserEmail)
        {
            string strReturn = strContent;
            string strTag = string.Empty;
            string strValue = string.Empty;
            int iCfgItemId = itemTarget.ID;

            using (SPWeb objSPWeb = itemTarget.Web)
            {
                foreach (SPField objSPField in itemTarget.Fields)
                {
                    try
                    {
                        strTag = string.Format("[{0}]", objSPField.Title);
                        if (strReturn.IndexOf(strTag, StringComparison.InvariantCultureIgnoreCase) < 0)
                            continue;

                        strValue = string.Empty;
                        if (objSPField.Type == SPFieldType.Lookup)
                        {
                            SPFieldLookupValue objLookupValue = (SPFieldLookupValue)itemTarget[objSPField.Title];
                            strValue = objLookupValue.LookupValue;
                        }
                        else if (objSPField.Type == SPFieldType.User)
                        {
                            SPFieldUserValue objUserValue = new SPFieldUserValue(objSPWeb, Convert.ToString(itemTarget[objSPField.Title]));
                            if (boolUserEmail)
                            {
                                strValue = objUserValue.User.Email;
                            }
                            else
                            {
                                strValue = objUserValue.User.Name;
                            }
                        }
                        else if (objSPField.Type == SPFieldType.URL)
                        {
                            SPFieldUrlValue objUrlValue = (SPFieldUrlValue)itemTarget[objSPField.Title];
                            strValue = string.Format(@"<a href='{0}'>{1}</a>", objUrlValue.Url, objUrlValue.Description);
                        }
                        else
                        {
                            strValue = Convert.ToString(itemTarget[objSPField.Title]);
                        }
                        strReturn = strReturn.Replace(strTag, strValue);
                    }
                    catch (Exception ex)
                    {
                        logTraceInfo(string.Format(@"ex, list title={0}, item id={1}, objSPField.Title={2}, strValue={3}, ItemId=", itemTarget.ParentList.Title, itemTarget.ID, objSPField.Title, strValue, iCfgItemId));
                        logTraceInfo(string.Format(@"ex.Message={0}", ex.Message));
                        logTraceInfo(string.Format(@"ex.StackTrace={0}", ex.StackTrace));

                        continue;
                    }
                }
            }

            return strReturn;
        }

        public static void SetToolbarTypeFreeForm(SPWeb objSPWeb, string strPageUrl, Guid guidStorageKey)
        {
            string storageKey = guidStorageKey.ToString("B").ToUpper();
            uint toolbarType = (uint)EFWorkflowTimer.ListViewWebPartToolbarType.FreeForm;

            if (String.IsNullOrEmpty(storageKey) || storageKey == Guid.Empty.ToString())
                throw new Exception("The webpart must be added to the page before this method (SetToolbarType) can be called");

            SPLimitedWebPartManager limitedWebPartManager = null;
            limitedWebPartManager = objSPWeb.GetLimitedWebPartManager(strPageUrl, PersonalizationScope.Shared);

            ListViewWebPart lvwp = null;
            lvwp = (ListViewWebPart)limitedWebPartManager.WebParts[guidStorageKey];

            string listId = lvwp.ListName;
            string viewId = lvwp.ViewGuid;

            // get the SPWebPartManager from SPLimitedWebPartManager
            PropertyInfo webPartManagerProp = typeof(SPLimitedWebPartManager).GetProperty(
              "WebPartManager",
              BindingFlags.NonPublic | BindingFlags.Instance);
            SPWebPartManager webPartManager = (SPWebPartManager)webPartManagerProp.GetValue(limitedWebPartManager, null);

            // get the SPWebPartConnection from SPWebPartManager
            PropertyInfo spWebPartsProp = typeof(SPWebPartManager).GetProperty(
              "SPWebParts",
              BindingFlags.NonPublic | BindingFlags.Instance);
            SPWebPartCollection webParts = (SPWebPartCollection)spWebPartsProp.GetValue(webPartManager, null);

            // Call the ApplyViewToListWebPart method on the SPWebPartConnection
            // internal void ApplyViewToListWebPart(
            //    string storageKey,
            //    string listID,
            //    string viewID,
            //    uint toolbarType,
            //    out uint flags,
            //    out uint baseViewId)
            MethodInfo applyViewToListWebPart = typeof(SPWebPartCollection).GetMethod(
              "ApplyViewToListWebPart",
              BindingFlags.NonPublic | BindingFlags.Instance,
              null,
              new[]{
        typeof(string),
        typeof(string),
        typeof(string),
        typeof(uint),
        typeof(uint).MakeByRefType(),
        typeof(uint).MakeByRefType()
      },
              null);
            object[] parameters = new object[6];
            parameters[0] = storageKey;
            parameters[1] = listId;
            parameters[2] = viewId;
            parameters[3] = toolbarType;

            applyViewToListWebPart.Invoke(webParts, parameters);
        }

        public static void ShowToolbar(SPWeb objSPWeb, string strPageUrl, Guid guidStorageKey)
        {
            SPLimitedWebPartManager manager = null;
            manager = objSPWeb.GetLimitedWebPartManager(strPageUrl, PersonalizationScope.Shared);

            object[] setToolbarTypeParams = { (uint)EFWorkflowTimer.ListViewWebPartToolbarType.ShowToolbar };
            ListViewWebPart objListViewWebPart = null;
            objListViewWebPart = (ListViewWebPart)manager.WebParts[guidStorageKey];

            PropertyInfo viewProp = objListViewWebPart.GetType().GetProperty("View", BindingFlags.Instance | BindingFlags.NonPublic);
            SPView webPartView = viewProp.GetValue(objListViewWebPart, null) as SPView;

            MethodInfo setToolbarType = webPartView.GetType().GetMethod("SetToolbarType",
                BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(uint) }, null);
            setToolbarType.Invoke(webPartView, setToolbarTypeParams);

            webPartView.Update();
            manager.SaveChanges(objListViewWebPart);
        }
        #endregion

        public void processOneListWorkflow(SPWeb objSPWebRoot, string strWebUrl, SPListItem itemTarget, string strWorkflowName, bool boolTraceHistory)
        {
            SPWorkflowAssociationCollection objWorkflowAssociationCollection = null;
            bool boolExecuted = false;
            SPContentType objItemContentType = null;

            try
            {
                logDebugHistory(objSPWebRoot, string.Format(@"processOneListWorkflow(), WorkflowName={0}, itemTarget.Url={1}", strWorkflowName, itemTarget.Url), WorkflowType_List, strWorkflowName);

                objItemContentType = itemTarget.ContentType;
                using (SPSite site = new SPSite(objSPWebRoot.Site.ID))
                {
                    if (strWebUrl == @"/")
                        strWebUrl = string.Empty;

                    using (SPWeb web = site.OpenWeb(strWebUrl))
                    {
                        SPWorkflowCollection objSPWorkflowCollection = site.WorkflowManager.GetItemActiveWorkflows(itemTarget);
                        foreach (SPWorkflow objSPWorkflow in objSPWorkflowCollection)
                        {
                            if (objSPWorkflow.ItemName.Equals(strWorkflowName, StringComparison.InvariantCultureIgnoreCase))
                            {
                                logDebugHistory(objSPWebRoot, string.Format(@"WorkflowName ({0}) is running on item (url={1}), skipped.", strWorkflowName, itemTarget.Url), WorkflowType_List, strWorkflowName);
                                return;
                            }
                        }
                        objWorkflowAssociationCollection = itemTarget.ParentList.WorkflowAssociations;
                        logDebugHistory(objSPWebRoot, string.Format(@"item (url={0}) has {1} WorkflowAssociations", itemTarget.Url, objWorkflowAssociationCollection.Count), WorkflowType_List, strWorkflowName);
                        boolExecuted = false;
                        foreach (SPWorkflowAssociation objWorkflowAssociation in objWorkflowAssociationCollection)
                        {
                            logDebugHistory(objSPWebRoot, string.Format(@"objWorkflowAssociation.BaseTemplate.Name={0}, objWorkflowAssociation.Name={1}", objWorkflowAssociation.BaseTemplate.Name, objWorkflowAssociation.Name), WorkflowType_List, strWorkflowName);
                            if (objWorkflowAssociation.Enabled == false)
                                continue;

                            if (objWorkflowAssociation.BaseTemplate.Name.Equals(strWorkflowName, StringComparison.InvariantCultureIgnoreCase))
                            {
                                try
                                {
                                    site.WorkflowManager.StartWorkflow(itemTarget, objWorkflowAssociation, objWorkflowAssociation.AssociationData, SPWorkflowRunOptions.Synchronous);
                                }
                                catch (ArgumentNullException WfEx)
                                {
                                    logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"ArgumentNullException.Message={0}", WfEx.Message));
                                    throw;
                                }
                                catch (ArgumentException WfEx)
                                {
                                    logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"ArgumentException.Message={0}", WfEx.Message));
                                    throw;
                                }
                                catch (UnauthorizedAccessException WfEx)
                                {
                                    logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"UnauthorizedAccessException.Message={0}", WfEx.Message));
                                    throw;
                                }
                                catch (SPException WfEx)
                                {
                                    logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"The item is currently locked due to a short term lock or check-out. List Name={0}, Item ID={1}",
                                        itemTarget.ParentList.Title, itemTarget.ID));
                                    logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"SPException.Message={0}", WfEx.Message));
                                    for (int i = 0; i < 3; i++)
                                    {
                                        System.Threading.Thread.Sleep(30000);
                                        try
                                        {
                                            site.WorkflowManager.StartWorkflow(itemTarget, objWorkflowAssociation, objWorkflowAssociation.AssociationData, SPWorkflowRunOptions.Synchronous);
                                        }
                                        catch (SPException WfEx2)
                                        {
                                            continue;
                                        }
                                        break;
                                    }
                                }
                                logDebugHistory(objSPWebRoot, string.Format(@"WorkflowName ({0}) is started on item (url={1}).", strWorkflowName, itemTarget.Url), WorkflowType_List, strWorkflowName);
                                boolExecuted = true;
                                break;
                            }
                        }
                        if (boolExecuted == false)
                        {
                            foreach (SPContentType objSPContentType in itemTarget.ParentList.ContentTypes)
                            {
                                if (objItemContentType.Name != objSPContentType.Name)
                                    continue;

                                objWorkflowAssociationCollection = objSPContentType.WorkflowAssociations;
                                logDebugHistory(objSPWebRoot, string.Format(@"content type ({0}) has {1} WorkflowAssociations", objSPContentType.Name, objWorkflowAssociationCollection.Count), WorkflowType_List, strWorkflowName);
                                foreach (SPWorkflowAssociation objWorkflowAssociation in objWorkflowAssociationCollection)
                                {
                                    logDebugHistory(objSPWebRoot, string.Format(@"objWorkflowAssociation.BaseTemplate.Name={0}, objWorkflowAssociation.Name={1}", objWorkflowAssociation.BaseTemplate.Name, objWorkflowAssociation.Name), WorkflowType_List, strWorkflowName);
                                    if (objWorkflowAssociation.BaseTemplate.Name.Equals(strWorkflowName, StringComparison.InvariantCultureIgnoreCase))
                                    {
                                        site.WorkflowManager.StartWorkflow(itemTarget, objWorkflowAssociation, objWorkflowAssociation.AssociationData, SPWorkflowRunOptions.Synchronous);
                                        logDebugHistory(objSPWebRoot, string.Format(@"WorkflowName ({0}) is started on item (url={1}).", strWorkflowName, itemTarget.Url), WorkflowType_List, strWorkflowName);
                                        boolExecuted = true;
                                        break;
                                    }
                                }
                            }
                        }

                        if (boolTraceHistory)
                        {
                            if (boolExecuted == true)
                            {
                                logWorkflowTimerHistory(web, itemTarget, "executed.");
                            }
                            else
                            {
                                logWorkflowTimerHistory(web, itemTarget, string.Format("Cannot find the list workflow '{0}' in list '{1}'.", strWorkflowName, itemTarget.ParentList.RootFolder.ServerRelativeUrl));
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                logTraceInfo(string.Format(@"itemTarget.Url={0}, strWorkflowName={1}, boolTraceHistory={2}, strWebUrl={3}", itemTarget.Url, strWorkflowName, boolTraceHistory, strWebUrl));
                logTraceInfo(string.Format(@"ex, list title={0}, item id={1}", itemTarget.ParentList.Title, itemTarget.ID));
                logTraceInfo(string.Format(@"ex.Message={0}", ex.Message));
                logTraceInfo(string.Format(@"ex.StackTrace={0}", ex.StackTrace));
                logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"Exception.Message={0}", ex.Message));
                logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"Exception.StackTrace={0}", ex.StackTrace));

                throw;
            }
        }

        public void processOneSiteWorkflow(SPWeb objSPWebRoot, string strWebUrl, string strWorkflowName, bool boolTraceHistory)
        {
            try
            {
                logDebugHistory(objSPWebRoot, string.Format(@"processOneSiteWorkflow(), WorkflowType={0}, WorkflowName={1}", WorkflowType_Site, strWorkflowName));

                using (SPSite site = new SPSite(objSPWebRoot.Site.ID))
                {
                    if (strWebUrl == @"/")
                        strWebUrl = string.Empty;

                    using (SPWeb web = site.OpenWeb(strWebUrl))
                    {
                        SPWorkflowAssociation objSPWorkflowAssociation = web.WorkflowAssociations.GetAssociationByName(strWorkflowName, CultureInfo.InvariantCulture);
                        if (objSPWorkflowAssociation == null)
                        {
                            logWorkflowTimerHistory(web, null, string.Format("Cannot find the site workflow '{0}' in web '{1}'.", strWorkflowName, strWebUrl));
                            return;
                        }

                        SPWorkflow objSPWorkflow = site.WorkflowManager.StartWorkflow(null, objSPWorkflowAssociation, string.Empty, SPWorkflowRunOptions.Synchronous);

                        if (boolTraceHistory)
                        {
                            logWorkflowTimerHistory(web, null, "succeeded");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                logTraceInfo(string.Format(@"strWorkflowType={0}, strWorkflowName={1}, boolTraceHistory={2}, strWebUrl={3}", WorkflowType_List, strWorkflowName, boolTraceHistory, strWebUrl));
                logTraceInfo(string.Format(@"ex.Message={0}", ex.Message));
                logTraceInfo(string.Format(@"ex.StackTrace={0}", ex.StackTrace));
                logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"Exception.Message={0}", ex.Message));
                logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"Exception.StackTrace={0}", ex.StackTrace));

                throw;
            }
        }

        public void markOneSPListItem(SPWeb objSPWebRoot, SPListItem itemTarget, int iItemCount)
        {
            itemTarget[Column_EFWorkflowTimerCfg_LastScanTime] = DateTime.Now;
            itemTarget[Column_EFWorkflowTimerCfg_LastScanCount] = iItemCount;
            itemTarget.SystemUpdate(false);
        }

        public short getWorkDayNumber(DateTime dtNow)
        {
            short iReturn = short.MinValue;

            // http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spregionalsettings.workdays.aspx
            switch (dtNow.Date.DayOfWeek)
            {
                case DayOfWeek.Sunday:
                    iReturn = 64;
                    break;
                case DayOfWeek.Monday:
                    iReturn = 32;
                    break;
                case DayOfWeek.Tuesday:
                    iReturn = 16;
                    break;
                case DayOfWeek.Wednesday:
                    iReturn = 8;
                    break;
                case DayOfWeek.Thursday:
                    iReturn = 4;
                    break;
                case DayOfWeek.Friday:
                    iReturn = 2;
                    break;
                case DayOfWeek.Saturday:
                    iReturn = 1;
                    break;
                default:
                    break;
            }

            return iReturn;
        }

        public bool InBusinessHour(SPWeb objSPWebRoot, DateTime dtNow, string strWorkflowName)
        {
            int iCurrentHour = int.MinValue;
            logDebugHistory(objSPWebRoot, string.Format(@"InBusinessHour(), Now:{0}, DayOfWeek:{1}, RegionalSettings.WorkDays:{2}", DateTime.Now, dtNow.Date.DayOfWeek, objSPWebRoot.RegionalSettings.WorkDays), string.Empty, strWorkflowName);

            logDebugHistory(objSPWebRoot, string.Format(@"InBusinessHour(), getWorkDayNumber(dtNow):{0}", getWorkDayNumber(dtNow)), string.Empty, strWorkflowName);
            if ((getWorkDayNumber(dtNow) & objSPWebRoot.RegionalSettings.WorkDays) == 0)
            {
                return false;
            }

            iCurrentHour = dtNow.Hour * 60 + dtNow.Minute;
            logDebugHistory(objSPWebRoot, string.Format(@"InBusinessHour(), iCurrentHour:{0}, objSPWebRoot.RegionalSettings.WorkDayStartHour:{1}, objSPWebRoot.RegionalSettings.WorkDayEndHour:{2}",
                iCurrentHour, objSPWebRoot.RegionalSettings.WorkDayStartHour, objSPWebRoot.RegionalSettings.WorkDayEndHour), string.Empty, strWorkflowName);
            if (iCurrentHour > objSPWebRoot.RegionalSettings.WorkDayStartHour
                && iCurrentHour < objSPWebRoot.RegionalSettings.WorkDayEndHour)
            {
                return true;
            }
            return false;
        }

        public void processOneJobitem(SPWeb objSPWebRoot, SPListItem objTimerItem)
        {
            string strWorkflowName = string.Empty;
            bool boolTraceHistory = false;
            bool boolIsActive = false;
            bool boolBussinessHourOnly = false;
            
            SPView spviewTarget = null;
            SPList splistTarget = null;
            SPListItem objCurrentItem = null;
            SPListItemCollection objSPListItemCollection = null;

            string strWebUrl = string.Empty;
            string strViewUrl = string.Empty;
            string strWorkflowType = string.Empty;
            int iScanCount = 0;

            try
            {
                _strTimerName = Convert.ToString(objTimerItem[SPBuiltInFieldId.Title]);

                logDebugHistory(objSPWebRoot, string.Format(@"processOneJobitem() begin. objTimerItem.Url={0}", objTimerItem.Url), string.Empty, strWorkflowName);

                boolIsActive = Convert.ToBoolean(objTimerItem[Column_EFWorkflowTimerCfg_IsActive]);
                if (false == boolIsActive)
                {
                    return;
                }

                strWebUrl = Convert.ToString(objTimerItem[Column_EFWorkflowTimerCfg_WebUrl]);
                strViewUrl = Convert.ToString(objTimerItem[Column_EFWorkflowTimerCfg_ViewUrl]);
                SPFieldChoice objWorkflowType = (SPFieldChoice)objTimerItem.Fields[Column_EFWorkflowTimerCfg_WorkflowType];
                strWorkflowType = objWorkflowType.GetFieldValueAsText(objTimerItem[Column_EFWorkflowTimerCfg_WorkflowType]);
                strWorkflowName = Convert.ToString(objTimerItem[Column_EFWorkflowTimerCfg_WorkflowName]);
                boolTraceHistory = Convert.ToBoolean(objTimerItem[Column_EFWorkflowTimerCfg_TraceHistory]);
                logDebugHistory(objSPWebRoot, string.Format(@"strWorkflowType={0}, strWorkflowName={1}, boolTraceHistory={2}, strViewUrl={3}", strWorkflowType, strWorkflowName, boolTraceHistory, strViewUrl), string.Empty, strWorkflowName);

                boolBussinessHourOnly = Convert.ToBoolean(objTimerItem[Column_EFWorkflowTimerCfg_BussinessHourOnly]);
                if (boolBussinessHourOnly)
                {
                    if (InBusinessHour(objSPWebRoot, DateTime.Now, strWorkflowName) == false)
                    {
                        logDebugHistory(objSPWebRoot, string.Format(@"The current time ({0}) is not in business hour", DateTime.Now), string.Empty, strWorkflowName);
                        return;
                    }
                }

                using (SPSite site = new SPSite(objSPWebRoot.Site.ID))
                {
                    if (strWebUrl == @"/")
                        strWebUrl = string.Empty;

                    using (SPWeb objSPWeb = site.OpenWeb(strWebUrl))
                    {
                        if (objSPWeb.Exists == false)
                        {
                            logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"Sub site {0} doesn't exist in site collection {1}.", strWebUrl, site.ServerRelativeUrl));
                            return;
                        }

                        if (string.IsNullOrEmpty(strViewUrl) && strWorkflowType.Equals(WorkflowType_Site, StringComparison.InvariantCultureIgnoreCase))
                        {
                            processOneSiteWorkflow(objSPWeb, strWebUrl, strWorkflowName, boolTraceHistory);
                            iScanCount = 1;
                        }
                        else
                        {
                            spviewTarget = objSPWeb.GetViewFromUrl(strViewUrl);
                            if (spviewTarget == null)
                            {
                                logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"List view {0} doesn't exist in web {1}.", strViewUrl, strWebUrl));
                            }

                            splistTarget = spviewTarget.ParentList;
                            objSPListItemCollection = splistTarget.GetItems(spviewTarget);

                            foreach (SPListItem itemTarget in objSPListItemCollection)
                            {
                                try
                                {
                                    objCurrentItem = splistTarget.GetItemById(itemTarget.ID);
                                    processOneListWorkflow(objSPWeb, strWebUrl, objCurrentItem, strWorkflowName, boolTraceHistory);
                                    iScanCount = objSPListItemCollection.Count;
                                }
                                catch (Exception ex)
                                {
                                    logTraceInfo(string.Format(@"objJobitem.Url={0}, objSPListItemCollection.Count={1}", objTimerItem.Url, objSPListItemCollection.Count));
                                    logTraceInfo(string.Format(@"strWorkflowType={0}, strWorkflowName={1}, boolTraceHistory={2}, strViewUrl={3}", strWorkflowType, strWorkflowName, boolTraceHistory, strViewUrl));
                                    logTraceInfo(string.Format(@"itemTarget.Url={0}", itemTarget.Url));
                                    logTraceInfo(string.Format(@"ex.Message={0}", ex.Message));
                                    logTraceInfo(string.Format(@"ex.StackTrace={0}", ex.StackTrace));

                                    logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"Exception.Message={0}", ex.Message));
                                    logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"Exception.StackTrace={0}", ex.StackTrace));

                                    continue;
                                }
                            }
                        }
                    }
                }

                markOneSPListItem(objSPWebRoot, objTimerItem, iScanCount);
            }
            catch (Exception ex)
            {
                logTraceInfo(string.Format(@"ex, objSPWeb.ServerRelativeUrl={0}, objJobitem.Url={1}", objSPWebRoot.ServerRelativeUrl, objTimerItem.Url));
                logTraceInfo(string.Format(@"ex.Message={0}", ex.Message));
                logTraceInfo(string.Format(@"ex.StackTrace={0}", ex.StackTrace));
                logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"Exception.Message={0}", ex.Message));
                logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"Exception.StackTrace={0}", ex.StackTrace));

                throw;
            }
        }

        public void processOneSiteCollection(SPSite objSPSite, string strRecurringType)
        {
            SPList splistTimer = null;
            SPList splistHistory = null;
            SPView objSPView = null;
            SPListItem objCurrentTimerItem = null;
            SPListItemCollection objSPListItemCollection = null;
            bool boolTraceHistory = false;

            using (SPWeb objSPWebRoot = objSPSite.RootWeb)
            {
                try
                {
                    splistHistory = objSPWebRoot.Lists.TryGetList(History_ListTitle);
                    if (splistHistory == null)
                        return;

                    _strJobName = this.Title;

                    //string strFullJobName = getFullJobName(objSPSite.Url);
                    //logDebugHistory(objSPWebRoot, string.Format(@"EFWorkflowTimer executed. FullJobName={0}, objSPWeb.ServerRelativeUrl={1}, strRecurringType={2}",
                    //    strFullJobName, objSPWebRoot.ServerRelativeUrl, strRecurringType));

                    splistTimer = objSPWebRoot.Lists.TryGetList(Configuration_ListTitle);

                    objSPView = splistTimer.Views[strRecurringType];
                    objSPListItemCollection = splistTimer.GetItems(objSPView);
                    logDebugHistory(objSPWebRoot, string.Format(@"strRecurringType={0}, objSPListItemCollection.Count={1}", strRecurringType, objSPListItemCollection.Count));

                    if (objSPListItemCollection.Count > 0)
                        logTraceInfo(string.Format(@"Job ({0}) begin......", _strJobName));

                    foreach (SPListItem objTimerItem in objSPListItemCollection)
                    {
                        try
                        {
                            objCurrentTimerItem = splistTimer.GetItemById(objTimerItem.ID);
                            if (true == Convert.ToBoolean(objCurrentTimerItem[Column_EFWorkflowTimerCfg_TraceHistory]))
                            {
                                boolTraceHistory = true;
                            }
                            processOneJobitem(objSPWebRoot, objCurrentTimerItem);
                        }
                        catch
                        {
                            continue;
                        }
                    }

                    if (objSPListItemCollection.Count > 0 && boolTraceHistory == true)
                        logTraceInfo(string.Format(@"Job ({0}) completed.", _strJobName));
                }
                catch (Exception ex)
                {
                    logTraceInfo(string.Format(@"ex, objSPSite.ServerRelativeUrl={0}", objSPSite.ServerRelativeUrl));
                    logTraceInfo(string.Format(@"ex.Message={0}", ex.Message));
                    logTraceInfo(string.Format(@"ex.StackTrace={0}", ex.StackTrace));
                    logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"Exception.Message={0}", ex.Message));
                    logWorkflowTimerHistory(objSPWebRoot, null, string.Format(@"Exception.StackTrace={0}", ex.StackTrace));

                    throw;
                }
            }
        }

        public void clearHistory(SPSite objSPSite)
        {
            SPList objHistory = null;
            objHistory = objSPSite.RootWeb.Lists.TryGetList(History_ListTitle);
            if (objHistory != null)
            {
                SPQuery objQuery = new SPQuery();
                string strDate = string.Empty;
                strDate = Microsoft.SharePoint.Utilities.SPUtility.CreateISO8601DateTimeFromSystemDateTime(DateTime.Today.AddMonths(-3));
                objQuery.Query = @"<Where><Lt><FieldRef Name=""Created""/><Value Type=""DateTime"">" + strDate + @"</Value></Lt></Where>";
                SPListItemCollection objSPListItemCollection = objHistory.GetItems(objQuery);
                if (objSPListItemCollection.Count > 0)
                {
                    for (int i = objSPListItemCollection.Count - 1; i >= 0; i--)
                    {
                        objSPListItemCollection.Delete(i);
                    }
                }
            }
        }

        public override void Execute(Guid contentDbId)
        {
            SPWebApplication webApplication = this.Parent as SPWebApplication;
            SPContentDatabase contentDb = webApplication.ContentDatabases[contentDbId];
            string strSiteName = string.Empty;
            string strRecurringType = string.Empty;

            strRecurringType = this.Title.Substring(this.Title.LastIndexOf(JobName_SeparatorChar) + 1);

            // get a reference to the "ListTimerJob" list in the RootWeb of the first site collection in the content database
            //foreach (SPSite objSPSite in webApplication.Sites)
            foreach (SPSite objSPSite in contentDb.Sites)
            {
                try
                {
                    strSiteName = getFullJobName(objSPSite.Url);
                    logDebugHistory(objSPSite.RootWeb, string.Format(@"this.Title={0}, strSiteName={1}", this.Title, strSiteName));
                    if (this.Title.StartsWith(strSiteName, StringComparison.InvariantCultureIgnoreCase) == false)
                    {
                        logDebugHistory(objSPSite.RootWeb, string.Format(@"site ({0}) ignored by {1}", strSiteName, this.Title));
                        continue;
                    }

                    processOneSiteCollection(objSPSite, strRecurringType);
                }
                catch
                {
                    continue;
                }
                finally
                {
                    objSPSite.Dispose();
                }

                //clear history
                if (this.Title.EndsWith(RecurringType_Monthly, StringComparison.InvariantCultureIgnoreCase))
                {
                    clearHistory(objSPSite);
                }
            }
        }
    }
}
